/*
 *  Copyright (C) 2010 Interactive Media Management
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.i2m.converge.jsf.beans;

import dk.i2m.converge.core.workflow.Department;
import dk.i2m.converge.core.workflow.Edition;
import dk.i2m.converge.core.calendar.Event;
import dk.i2m.converge.core.content.NewsItem;
import dk.i2m.converge.core.workflow.Outlet;
import dk.i2m.converge.core.workflow.Section;
import dk.i2m.converge.core.security.SystemPrivilege;
import dk.i2m.converge.core.security.UserAccount;
import dk.i2m.converge.core.workflow.WorkflowState;
import dk.i2m.converge.ejb.facades.CalendarFacadeLocal;
import dk.i2m.converge.ejb.facades.LockingException;
import dk.i2m.converge.ejb.facades.NewsItemFacadeLocal;
import dk.i2m.converge.ejb.facades.NewsItemHolder;
import dk.i2m.converge.ejb.facades.OutletFacadeLocal;
import dk.i2m.converge.ejb.facades.SearchEngineLocal;
import dk.i2m.converge.ejb.facades.UserFacadeLocal;
import dk.i2m.converge.ejb.facades.WorkflowFacadeLocal;
import dk.i2m.converge.ejb.facades.WorkflowStateTransitionException;
import dk.i2m.converge.jsf.components.tags.DialogAssignment;
import dk.i2m.converge.jsf.components.tags.DialogEventSelection;
import dk.i2m.converge.jsf.components.tags.UIEvent;
import dk.i2m.converge.jsf.components.tags.UIEventListener;
import dk.i2m.converge.utils.CalendarUtils;
import dk.i2m.converge.ejb.services.DataNotFoundException;
import dk.i2m.jsf.JsfUtils;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.event.ActionEvent;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

/**
 * Managed backing bean for the planning view.
 *
 * @author Allan Lykke Christensen
 */
public class Planning implements UIEventListener {

    private static final Logger log = Logger.getLogger(Planning.class.getName());

    @EJB private OutletFacadeLocal outletFacade;

    @EJB private UserFacadeLocal userFacade;

    @EJB private CalendarFacadeLocal calendarFacade;

    @EJB private NewsItemFacadeLocal newsItemFacade;

    @EJB private WorkflowFacadeLocal workflowFacade;

    @EJB private SearchEngineLocal searchEngine;

    private DialogAssignment dialogAssignment;

    private DialogEventSelection dialogEventSelection = null;

    private Outlet selectedOutlet;

    private Department selectedDepartment;

    private WorkflowState selectedState;

    private Calendar selectedDate;

    private List<Edition> selectedEditions;

    private Edition selectedEdition;

    private Date selectedCalendarDate;

    private Calendar useExistingEventDate;

    private Event selectedEventFromCalendar;

    private Outlet moveOutlet;

    private NewsItem selectedAssignment;

    private DataModel pipeline = new ListDataModel();

    private List<UserAccount> selectedUsers;

    private DataModel openAssignments = null;

    /** 
     * Creates a new instance of {@link Planning}.
     */
    public Planning() {
    }

    @PostConstruct
    public void onInit() {
        selectedDate = Calendar.getInstance();
        selectedDate.setTimeZone(getUser().getTimeZone());
        selectedDate.add(Calendar.DAY_OF_MONTH, 1);
        useExistingEventDate = Calendar.getInstance();

        selectedOutlet = getUser().getDefaultOutlet();

        if (selectedOutlet == null) {
            if (!getOutlets().isEmpty()) {
                selectedOutlet = getOutlets().iterator().next();
            }
        }

        fetchEditions();
    }

    /**
     * Event handler for when an {@link Outlet} is selected.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onSelectOutlet(ActionEvent event) {
        fetchEditions();
    }

    /**
     * Event handler for when a date is selected from the calendar.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onSelectDate(ActionEvent event) {
        fetchEditions();
    }

    public void onNewEdition(ActionEvent event) {
        if (isOutletSelected() && isDateSelected()) {
            Calendar startDate = Calendar.getInstance();
            startDate.setTimeZone(getUser().getTimeZone());
            startDate.setTime(selectedDate.getTime());
            startDate.set(Calendar.HOUR_OF_DAY, 0);
            startDate.set(Calendar.MINUTE, 0);
            startDate.set(Calendar.SECOND, 0);

            Calendar endDate = Calendar.getInstance();
            endDate.setTimeZone(getUser().getTimeZone());
            endDate.setTime(selectedDate.getTime());
            endDate.set(Calendar.HOUR_OF_DAY, 23);
            endDate.set(Calendar.MINUTE, 59);
            endDate.set(Calendar.SECOND, 59);

            selectedEdition = new Edition();
            selectedEdition.setOutlet(selectedOutlet);
            selectedEdition.setPublicationDate(startDate);
            selectedEdition.setExpirationDate(endDate);
            selectedEdition.setOpen(true);
        }
    }

    public void onDeleteEdition(ActionEvent event) {
        outletFacade.deleteEdition(selectedEdition.getId());
        fetchEditions();
    }

    public void onSaveEdition(ActionEvent event) {
        if (isEditionAddMode()) {
            selectedEdition = outletFacade.createEdition(selectedEdition);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "planning_EDITION_SAVED");
        } else {
            outletFacade.updateEdition(selectedEdition);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "planning_EDITION_UPDATED");
        }

        fetchEditions();
    }

    public void onNewEditionPlacement(ActionEvent event) {
        onNewAssignment(event);
        dialogAssignment.getAssignment().setEdition(selectedEdition);
        dialogAssignment.onChangeEdition(null);
//        dialogAssignment.getAssignment().setSection(selectedEditionSection);
    }

    public void onSaveEditionPlacement(ActionEvent event) {
        if (isAssignmentAddMode()) {
            try {
                selectedAssignment = newsItemFacade.start(selectedAssignment);
                JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "planning_EDITION_PLACEMENT_CREATED");
            } catch (WorkflowStateTransitionException ex) {
                Logger.getLogger(Planning.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            try {
                selectedAssignment = newsItemFacade.checkin(selectedAssignment);
                JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "planning_EDITION_PLACEMENT_UPDATED");
            } catch (LockingException ex) {
                JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_ERROR, false, ex.getMessage(), null);
            }
        }
        fetchEditions();

    }

    public void onDeleteEditionPlacement(ActionEvent event) {
        if (selectedAssignment.getActors().isEmpty()) {
            newsItemFacade.deleteNewsItem(selectedAssignment.getId());
        } else {
            selectedAssignment.setEdition(null);
            selectedAssignment.setSection(null);
            selectedAssignment.setPosition(0);
            selectedAssignment.setStart(0);
            try {
                selectedAssignment = newsItemFacade.checkin(selectedAssignment);
            } catch (LockingException ex) {
                JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_ERROR, false, ex.getMessage(), null);
            }
        }

        JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "planning_EDITION_PLACEMENT_DELETED");
        fetchEditions();
    }

    /**
     * Gets a {@link List} of the {@link Outlet}s where the current user has
     * outlet planning privileges.
     *
     * @return {@link List} of the {@link Outlet}s where the current user has
     *         outlet planning privileges
     */
    public List<Outlet> getOutlets() {
        UserAccount currentUser = (UserAccount) JsfUtils.getValueOfValueExpression("#{userSession.user}");
        return currentUser.getPrivilegedOutlets(SystemPrivilege.OUTLET_PLANNING);
    }

    /**
     * Gets a {@link Map} of the {@link Outlet}s where the current user has
     * outlet planning privileges.
     *
     * @return {@link Map} of the {@link Outlet}s where the current user has
     *         outlet planning privileges
     */
    public Map<String, Outlet> getOutletsMap() {
        Map<String, Outlet> outlets = new LinkedHashMap<String, Outlet>();
        UserAccount currentUser = (UserAccount) JsfUtils.getValueOfValueExpression("#{userSession.user}");
        for (Outlet outlet : currentUser.getPrivilegedOutlets(SystemPrivilege.OUTLET_PLANNING)) {
            outlets.put(outlet.getTitle(), outlet);
        }
        return outlets;
    }

    /**
     * Gets a {@link Map} of potential authors for a {@link NewsItem} of the
     * selected outlet.
     *
     * @return {@link Map} of {@link UserAccount}s that are potential authors
     *         of a new {@link NewsItem} for the selected outlet
     */
    public Map<String, UserAccount> getAuthors() {
        Map<String, UserAccount> authors = new LinkedHashMap<String, UserAccount>();

        WorkflowState start = selectedOutlet.getWorkflow().getStartState();

        List<UserAccount> potentialAuthors = userFacade.getMembers(start.getActorRole());

        for (UserAccount acc : potentialAuthors) {
            authors.put(acc.getFullName(), acc);
        }

        return authors;
    }

    public Map<String, Department> getOutletDepartments() {
        Map<String, Department> departments = new LinkedHashMap<String, Department>();
        if (selectedOutlet != null) {
            for (Department dep : selectedOutlet.getDepartments()) {
                departments.put(dep.getName(), dep);
            }
        }

        return departments;
    }

    public Map<String, UserAccount> getOutletDepartmentEditors() {
        Map<String, UserAccount> editors = new LinkedHashMap<String, UserAccount>();

        if (selectedAssignment.getDepartment() != null) {
            List<UserAccount> members = userFacade.getMembers(selectedAssignment.getDepartment().getId());

            for (UserAccount acc : members) {
                editors.put(acc.getFullName(), acc);
            }
        }

        return editors;
    }

    public Outlet getSelectedOutlet() {
        return selectedOutlet;
    }

    public void setSelectedOutlet(Outlet selectedOutlet) {
        this.selectedOutlet = selectedOutlet;
        this.openAssignments = null;
    }

    public Calendar getSelectedDate() {
        return selectedDate;
    }

    public void setSelectedDate(Calendar selectedDate) {
        this.selectedDate = selectedDate;
    }

    public List<Edition> getSelectedEditions() {
        return selectedEditions;
    }

    public void setSelectedEditions(List<Edition> selectedEditions) {
        this.selectedEditions = selectedEditions;
    }

    public Edition getSelectedEdition() {
        return selectedEdition;
    }

    public void setSelectedEdition(Edition selectedEdition) {
        this.selectedEdition = selectedEdition;
    }

    public boolean isEditionEditMode() {
        if (selectedEdition == null || selectedEdition.getId() == null) {
            return false;
        } else {
            return true;
        }
    }

    public boolean isEditionAddMode() {
        return !isEditionEditMode();
    }

    /**
     * Determines if there is an {@link Edition} available for the selected
     * {@link Outlet} and date.
     *
     * @return <code>true</code> if there is an {@link Edition} available for
     *         the selected {@link Outlet} and date, otherwise <code>false</code>
     */
    public boolean isEditionAvailable() {
        if (selectedEditions == null || selectedEditions.isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Determines if a date has been selected.
     *
     * @return <code>true</code> if a date has been selected, otherwise
     *         <code>false</code>
     */
    public boolean isDateSelected() {
        if (selectedDate == null) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Determines if an {@link Outlet} has been selected.
     *
     * @return <code>true</code> if an {@link Outlet} has been selected, 
     *         otherwise <code>false</code>
     */
    public boolean isOutletSelected() {
        if (selectedOutlet == null) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Gets a formatted title for the Edition bar.
     *
     * @return formatted title for the Edition bar
     */
    public String getEditionTitle() {
        String pattern = JsfUtils.getResourceBundle().getString("planning_EDITION_TITLE");
        return MessageFormat.format(pattern, selectedOutlet.getTitle(), selectedDate.getTime());
    }

    public Date getSelectedCalendarDate() {
        return selectedCalendarDate;
    }

    public void setSelectedCalendarDate(Date selectedCalendarDate) {
        this.selectedCalendarDate = selectedCalendarDate;
    }

    private UserAccount getUser() {
        return (UserAccount) JsfUtils.getValueOfValueExpression("#{userSession.user}");
    }

    private void fetchEditions() {
        if (isOutletSelected() && isDateSelected()) {
            selectedEditions = outletFacade.findEditionByOutletAndDate(selectedOutlet.getId(), selectedDate);
        } else {
            log.log(Level.FINEST, "Outlet [{0}] or date [{1}] is not selected", new Object[]{selectedOutlet, selectedDate});
        }
    }

    public DataModel getSchedule() {
        Calendar start = CalendarUtils.getFirstDayOfMonth(useExistingEventDate);
        Calendar end = CalendarUtils.getLastDayOfMonth(useExistingEventDate);
        return new ListDataModel(calendarFacade.findByDate(start, end));
    }

    public Calendar getUseExistingEventDate() {
        return useExistingEventDate;
    }

    public void setUseExistingEventDate(Calendar useExistingEventDate) {
        this.useExistingEventDate = useExistingEventDate;
    }

    public Event getSelectedEventFromCalendar() {
        return selectedEventFromCalendar;
    }

    public void setSelectedEventFromCalendar(Event selectedEventFromCalendar) {
        this.selectedEventFromCalendar = selectedEventFromCalendar;
    }

    public void onSelectEventFromCalendar(ActionEvent evet) {
        if (selectedEventFromCalendar == null) {
            return;
        }

        String msg = JsfUtils.getResourceBundle().getString("planning_COVER_X_EVENT");
        String title = MessageFormat.format(msg, selectedEventFromCalendar.getSummary());

        this.selectedAssignment.setTitle(title);
        this.selectedAssignment.setEvent(selectedEventFromCalendar);
    }

    public Map<String, Edition> getOutletEditionsMap() {
        Map<String, Edition> editionMap = new LinkedHashMap<String, Edition>();
        if (moveOutlet != null) {
            List<Edition> editions = outletFacade.findEditionsByStatus(true, moveOutlet);

            for (Edition edition : editions) {
                editionMap.put(edition.getFriendlyName(), edition);
            }
        }
        return editionMap;
    }

    public Map<String, Section> getOutletSectionsMap() {
        Map<String, Section> activeSections = new LinkedHashMap<String, Section>();

        for (Section section : selectedOutlet.getSections()) {
            if (section.isActive()) {
                activeSections.put(section.getFullName(), section);
            }
        }

        return activeSections;
    }

    // ------------------------------------------------------------------------
    public void onNewAssignment(ActionEvent event) {
        onRefreshOpenAssignments(event);

        dialogAssignment = new DialogAssignment(outletFacade, workflowFacade, userFacade, newsItemFacade, calendarFacade, getOutlets());
        dialogAssignment.addListener(this);
        dialogAssignment.showStoryTab();
        dialogAssignment.getAssignment().setAssigned(true);
        dialogAssignment.getAssignment().setAssignedBy(getUser());
        dialogAssignment.getAssignment().setOutlet(selectedOutlet);
        dialogAssignment.onChangeOutlet(null);

        if (selectedDate != null) {
            dialogAssignment.getAssignment().setDeadline(selectedDate);
            dialogAssignment.getAssignment().getDeadline().setTimeZone(getUser().getTimeZone());
            dialogAssignment.getAssignment().getDeadline().set(Calendar.HOUR_OF_DAY, 15);
            dialogAssignment.getAssignment().getDeadline().set(Calendar.MINUTE, 0);
            dialogAssignment.getAssignment().getDeadline().set(Calendar.SECOND, 0);
        } else {
            dialogAssignment.getAssignment().setDeadline(Calendar.getInstance());
            dialogAssignment.getAssignment().getDeadline().setTimeZone(getUser().getTimeZone());
            dialogAssignment.getAssignment().getDeadline().set(Calendar.HOUR_OF_DAY, 15);
            dialogAssignment.getAssignment().getDeadline().set(Calendar.MINUTE, 0);
            dialogAssignment.getAssignment().getDeadline().set(Calendar.SECOND, 0);
        }
        dialogEventSelection = new DialogEventSelection(calendarFacade);
        dialogEventSelection.setAssignment(dialogAssignment.getAssignment());
    }

    public void onShowBriefing(ActionEvent event) {
        dialogAssignment = new DialogAssignment(outletFacade, workflowFacade, userFacade, newsItemFacade, calendarFacade, getOutlets());
        dialogAssignment.showBriefingTab();
        dialogAssignment.setAssignment(selectedAssignment);
        dialogAssignment.onChangeOutlet(null);
        dialogEventSelection = new DialogEventSelection(calendarFacade);
        dialogEventSelection.setAssignment(dialogEventSelection.getAssignment());
    }

    public void onShowAssignment(ActionEvent event) {
        dialogEventSelection = new DialogEventSelection(calendarFacade);
        dialogAssignment = new DialogAssignment(outletFacade, workflowFacade, userFacade, newsItemFacade, calendarFacade, getOutlets());
        dialogAssignment.showStoryTab();
        dialogAssignment.setAssignment(selectedAssignment);
        dialogAssignment.onChangeOutlet(null);
        dialogEventSelection = new DialogEventSelection(calendarFacade);
        dialogEventSelection.setAssignment(dialogAssignment.getAssignment());
    }

    public void onDeleteAssignment(ActionEvent event) {
        if (newsItemFacade.deleteNewsItem(selectedAssignment.getId())) {
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "planning_ASSIGNMENT_DELETED");
        } else {
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_ERROR, "planning_ASSIGNMENT_COULD_NOT_BE_DELETED");
        }
        onRefreshOpenAssignments(event);
    }

    public NewsItem getSelectedAssignment() {
        return selectedAssignment;
    }

    public void setSelectedAssignment(NewsItem selectedAssignment) {
        this.selectedAssignment = selectedAssignment;

        try {
            NewsItemHolder nih = newsItemFacade.checkout(this.selectedAssignment.getId());

            dialogEventSelection = new DialogEventSelection(calendarFacade);
            dialogAssignment = new DialogAssignment(outletFacade, workflowFacade, userFacade, newsItemFacade, calendarFacade, getOutlets());
            dialogAssignment.showStoryTab();
            dialogAssignment.setNewsItemHolder(nih);
            dialogAssignment.onChangeOutlet(null);
            dialogEventSelection = new DialogEventSelection(calendarFacade);
            dialogEventSelection.setAssignment(dialogAssignment.getAssignment());
        } catch (DataNotFoundException ex) {
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_ERROR, false, ex.getMessage(), null);
        }
    }

    public boolean isAssignmentEditMode() {
        if (selectedAssignment == null || selectedAssignment.getId() == null) {
            return false;
        } else {
            return true;
        }
    }

    public boolean isAssignmentAddMode() {
        return !isAssignmentEditMode();
    }

    public void onRefreshOpenAssignments(ActionEvent event) {
        this.openAssignments = null;
        getOpenAssignments();
    }

    public DataModel getOpenAssignments() {
        if (openAssignments == null) {
            openAssignments = new ListDataModel(newsItemFacade.findAssignmentsByOutlet(selectedOutlet));
        }
        return openAssignments;
    }

    public Map<String, UserAccount> getAssignmentOutletDepartmentEditors() {
        Map<String, UserAccount> editors = new LinkedHashMap<String, UserAccount>();

        if (selectedAssignment.getDepartment() != null) {
            List<UserAccount> members = userFacade.getMembers(selectedAssignment.getDepartment().getId());

            for (UserAccount acc : members) {
                editors.put(acc.getFullName(), acc);
            }
        }

        return editors;
    }

    public Department getSelectedDepartment() {
        return selectedDepartment;
    }

    public void setSelectedDepartment(Department selectedDepartment) {
        this.selectedDepartment = selectedDepartment;
    }

    public WorkflowState getSelectedState() {
        return selectedState;
    }

    public void setSelectedState(WorkflowState selectedState) {
        this.selectedState = selectedState;
    }

    public Map<String, WorkflowState> getOutletWorkflowStates() {
        Map<String, WorkflowState> workflowStates = new LinkedHashMap<String, WorkflowState>();

        if (selectedOutlet != null) {
            for (WorkflowState state : selectedOutlet.getWorkflow().getStates()) {
                workflowStates.put(state.getName(), state);
            }
        }

        return workflowStates;
    }

    public void onRefreshPipeline(ActionEvent event) {
        if (selectedOutlet == null) {
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_ERROR, "planning_SELECT_OUTLET_ERROR");
        } else if (selectedState == null) {
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_ERROR, "planning_SELECT_STATE_ERROR");
        } else {
            List<NewsItem> items;
            if (selectedDepartment != null) {
                items = newsItemFacade.findByStateOutletAndDepartment(selectedState, selectedOutlet, selectedDepartment);
            } else {
                items = newsItemFacade.findByStateAndOutlet(selectedState, selectedOutlet);
            }

            pipeline = new ListDataModel(items);
        }
    }

    public DataModel getPipeline() {
        return pipeline;
    }

    public List<UserAccount> getSelectedUsers() {
        return selectedUsers;
    }

    public void setSelectedUsers(List<UserAccount> selectedUsers) {
        this.selectedUsers = selectedUsers;
    }

    public DialogAssignment getDialogAssignment() {
        return dialogAssignment;
    }

    public void setDialogAssignment(DialogAssignment dialogAssignment) {
        this.dialogAssignment = dialogAssignment;
    }

    public DialogEventSelection getDialogEventSelection() {
        if (dialogEventSelection == null) {
            dialogEventSelection = new DialogEventSelection(calendarFacade);
        }
        return dialogEventSelection;
    }

    public void setDialogEventSelection(DialogEventSelection dialogEventSelection) {
        this.dialogEventSelection = dialogEventSelection;
    }

    @Override
    public void onUIEvent(UIEvent event) {
        if (event.getType().equalsIgnoreCase("onsaveassignment")) {
            onRefreshOpenAssignments(null);
        }
    }
}
